/*
    Copyright (c) 2006-2007 Ivaylo Ivanov

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
*/
package com.avalonsql.core;

import static com.avalonsql.core.db.Const.*;
import com.avalonsql.core.db.QueryResultMetaData;
import com.avalonsql.core.db.QueryResults;
import com.avalonsql.core.db.objects.impl.DBObject;
import com.avalonsql.core.db.objects.impl.MetaData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

@SuppressWarnings({"StaticNonFinalField", "UtilityClass", "ChainedMethodCall", "LocalCanBeFinal", "NestedMethodCall", "UnqualifiedStaticUsage", "WeakerAccess", "ProtectedField", "Since15", "ClassWithoutLogger"})
public class XMLProvider {

    protected static DocumentBuilder documentBuilder;
    protected static Transformer transformer;
    protected static TransformerFactory transformerFactory;

    private XMLProvider() {
    }

    /**
     * @return instance of the DocumentBuilder class
     */
    public static synchronized DocumentBuilder getDocumentBuilder() throws XMLProviderException {
        if (documentBuilder == null) {
            try {
                DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
                documentBuilder = dFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new XMLProviderException(e);
            }
        }
        return documentBuilder;
    }

    /**
     * @return instance of the Transformer class
     */
    public static Transformer getTransformer() throws XMLProviderException {
        if (transformer == null) {
            try {
                transformer = getTransformerFactory().newTransformer();
            } catch (TransformerConfigurationException e) {
                throw new XMLProviderException(e);
            }
        }
        return transformer;
    }

    /**
     * @return instance of the TransformerFactory class
     */
    public static synchronized TransformerFactory getTransformerFactory() {
        if (transformerFactory == null) {
            transformerFactory = TransformerFactory.newInstance();
        }
        return transformerFactory;
    }

    /**
     * @param dbObjectsByType
     * @return document with all first level childs of dbObject with given name and class as its nodes
     */
    public static Document createDocument(Map<Class<? extends DBObject>, List<DBObject>> dbObjectsByType)
            throws XMLProviderException {
        if (dbObjectsByType == null) {
            throw new IllegalArgumentException("dbObjectsByType = null");
        }
        Document doc = getDocumentBuilder().newDocument();
        Element root = doc.createElement(RESULT_ROOT_NAME);
        doc.appendChild(root);

        for (Class<? extends DBObject> dbObjectsClass : dbObjectsByType.keySet()) {
            String className = dbObjectsClass.getSimpleName();
            Element packageElement = doc.createElement(className + PACKAGES_SUFFIX);

            MetaData annotation = dbObjectsClass.getAnnotation(MetaData.class);
            String classCaption = annotation.caption();

            packageElement.setAttribute(ATTRIBUTE_NAME, classCaption + PACKAGES_SUFFIX);
            root.appendChild(packageElement);

            for (DBObject dbObject : dbObjectsByType.get(dbObjectsClass)) {
                Element element = doc.createElement(className);
                element.setAttribute(ATTRIBUTE_NAME, dbObject.getName());
                Method[] methods = dbObject.getClass().getMethods();
                for (Method method : methods) {
                    String methodName = method.getName();
                    if (methodName.startsWith("get") && !"getClass".equals(methodName) && !"getClass".equals(methodName) && !"getName".equals(methodName) || methodName.startsWith("is")) {
                        try {
                            element.setAttribute(
                                    methodName.replace("get", "").replace("is", "").toLowerCase(), String.valueOf(method.invoke(dbObject))
                            );
                        } catch (IllegalAccessException e) {
                            throw new XMLProviderException(e);
                        } catch (InvocationTargetException e) {
                            throw new XMLProviderException(e);
                        }
                    }
                }
                element.setAttribute(ATTRIBUTE_SOURCE, dbObject.getSource());
                packageElement.appendChild(element);
            }
        }
        return doc;
    }

    /**
     * @param queryResults
     * @return document with all first level childs of dbObject with given name and class as its nodes
     */
    public static Document createDocument(QueryResults queryResults) throws XMLProviderException {
        if (queryResults == null) {
            throw new IllegalArgumentException("queryResults = null");
        }
        Document doc = getDocumentBuilder().newDocument();
        Element root = doc.createElement(RESULT_ROOT_NAME + "s");
        doc.appendChild(root);
        Node rowNode = root.appendChild(doc.createElement(RESULT_METADATA_NAME));
        for (QueryResultMetaData queryResultMetaData : queryResults.getMetaData()) {
            Element element = doc.createElement(RESULT_COLUMN_NAME);
            element.setAttribute(RESULT_ATTRIBUTR_TYPE, queryResultMetaData.getTypeName());
            Node valueNode = rowNode.appendChild(element);
            valueNode.setTextContent(queryResultMetaData.getName());
        }
        for (String[] columnValues : queryResults.getData()) {
            rowNode = root.appendChild(doc.createElement(RESULT_ROW_NAME));
            for (String columnValue : columnValues) {
                Node valueNode = rowNode.appendChild(doc.createElement(RESULT_VALUE_NAME));
                valueNode.setTextContent(columnValue);
            }
        }
        return doc;
    }

    /**
     * Prints given document to given output stream
     *
     * @param out
     * @param doc
     */
    public static void printDocument(Document doc, OutputStream out) throws XMLProviderException {
        if (doc == null) {
            throw new IllegalArgumentException("doc = null");
        }
        if (out == null) {
            throw new IllegalArgumentException("out = null");
        }
        try {
            getTransformer().transform(new DOMSource(doc), new StreamResult(out));
        } catch (TransformerException e) {
            throw new XMLProviderException(e);
        } catch (XMLProviderException e) {
            throw new XMLProviderException(e);
        }
    }


}
